﻿Imports System.Configuration

''' <summary>
''' Rule Set which can define a Business Object
''' </summary>
''' <typeparam name="InputType"></typeparam>
''' <remarks></remarks>
Public Class RuleSet(Of InputType)
    Inherits ConfigurationSection

#Region "Config Sections"

    'XML Key Constant
    Private Const BaseRuleSetKey As String = "baseruleset"
    Private Const NameKey As String = "name"
    Private Const DescKey As String = "desc"
    Private Const CustomKey As String = "custom"


    ''' <summary>
    ''' All registered rules which usable in config file
    ''' </summary>
    ''' <remarks>
    ''' This piece of code unfortunately duplicated to improve the usability of the class. Every new hard coded rule should be added to here to work.
    ''' </remarks>
    Private RegisteredRules() As IRule(Of String) = {Me.IsInRange, Me.IsDataInRange, Me.IsPositiveInteger, Me.IsNegativeInteger, Me.IsNotNull, Me.WhiteListCharacters, Me.WhiteListItems}

    ''' <summary>
    ''' Gets or sets the name.
    ''' </summary>
    ''' <value>The name of the RuleSet.</value>
    <ConfigurationProperty(NameKey)> _
    Public Property Name() As String
        Get
            Return DirectCast(Me.Item(NameKey), String)
        End Get
        Set(ByVal Value As String)
            Me.Item(NameKey) = Value
        End Set
    End Property



    ''' <summary>
    ''' Gets or sets the name.
    ''' </summary>
    ''' <value>The name of the RuleSet.</value>
    <ConfigurationProperty(BaseRuleSetKey)> _
    Public Property BaseRuleSet() As String
        Get
            Return DirectCast(Me.Item(BaseRuleSetKey), String)
        End Get
        Set(ByVal Value As String)
            Me.Item(BaseRuleSetKey) = Value
        End Set
    End Property



    ''' <summary>
    ''' Gets or sets the description.
    ''' </summary>
    ''' <value>The description of the RuleSet.</value>
    <ConfigurationProperty(DescKey)> _
    Public Property Desc() As String
        Get
            Return DirectCast(Me.Item(DescKey), String)
        End Get
        Set(ByVal Value As String)
            Me.Item(DescKey) = Value
        End Set
    End Property

    ''' <summary>
    ''' Gets the Custom Rules Definition Collection.
    ''' </summary>
    ''' <value>Custom Rules Collection.</value>
    <ConfigurationProperty(CustomKey, IsDefaultCollection:=False)> _
    Public ReadOnly Property CustomRuleCollection() As CustomRuleCollection
        Get
            Return CType(Me(CustomKey), CustomRuleCollection)
        End Get
    End Property

#End Region

#Region "Hardcoded Rules in the Configuration"


    ''' <summary>
    ''' Gets the IsInRange() rule.
    ''' </summary>
    ''' <value>The IsInRange() Rule.</value>
    <ConfigurationProperty("IsInNumericRange")> _
    Public ReadOnly Property IsInRange() As Rules.IsInNumericRange
        Get
            Return CType(MyBase.Item("IsInNumericRange"), Rules.IsInNumericRange)
        End Get
    End Property

    ''' <summary>
    ''' Gets the IsDataInRange() rule.
    ''' </summary>
    ''' <value>The IsDataInRange() Rule.</value>
    <ConfigurationProperty("IsDataInRange")> _
    Public ReadOnly Property IsDataInRange() As Rules.IsDataInRange
        Get
            Return CType(MyBase.Item("IsDataInRange"), Rules.IsDataInRange)
        End Get
    End Property

    ''' <summary>
    ''' Gets the IsPositiveInteger() rule.
    ''' </summary>
    ''' <value>The IsPositiveInteger() Rule.</value>
    <ConfigurationProperty("IsPositiveInteger")> _
    Public ReadOnly Property IsPositiveInteger() As Rules.IsPositiveInteger
        Get
            Return CType(MyBase.Item("IsPositiveInteger"), Rules.IsPositiveInteger)
        End Get
    End Property


    ''' <summary>
    ''' Gets the IsNegativeInteger() Rule.
    ''' </summary>
    ''' <value>The IsNegativeInteger() Rule.</value>
    <ConfigurationProperty("IsNegativeInteger")> _
    Public ReadOnly Property IsNegativeInteger() As Rules.IsNegativeInteger
        Get
            Return CType(MyBase.Item("IsNegativeInteger"), Rules.IsNegativeInteger)
        End Get
    End Property


    ''' <summary>
    ''' Gets the IsNotNull() Rule.
    ''' </summary>
    ''' <value>The IsNotNull() Rule.</value>
    <ConfigurationProperty("IsNotNull")> _
    Public ReadOnly Property IsNotNull() As Rules.IsNotNull
        Get
            Return CType(MyBase.Item("IsNotNull"), Rules.IsNotNull)
        End Get
    End Property


    ''' <summary>
    ''' Gets the WhiteListCharacters() Rule.
    ''' </summary>
    ''' <value>The WhiteListCharacters() Rule.</value>
    <ConfigurationProperty("WhiteListCharacters")> _
    Public ReadOnly Property WhiteListCharacters() As Rules.WhiteListCharacters
        Get
            Return CType(MyBase.Item("WhiteListCharacters"), Rules.WhiteListCharacters)
        End Get
    End Property


    ''' <summary>
    ''' Gets the WhiteListItems() Rule.
    ''' </summary>
    ''' <value>The WhiteListItems() Rule.</value>
    <ConfigurationProperty("WhiteListItems")> _
    Public ReadOnly Property WhiteListItems() As Rules.WhiteListItems
        Get
            Return CType(MyBase.Item("WhiteListItems"), Rules.WhiteListItems)
        End Get
    End Property

#End Region

#Region "Constructors"

    ''' <summary>
    ''' Initializes a new instance of the <see cref="RuleSet(Of InputType)" /> class.
    ''' </summary>
    Public Sub New()
    End Sub


    ''' <summary>
    ''' Initializes a new instance of the <see cref="RuleSet(Of InputType)" /> class from supplied RuleSet.
    ''' </summary>
    ''' <param name="ruleSet">The ruleSet to base.</param>
    Public Sub New(ByVal ruleSet As RuleSet(Of InputType))
        Me.New()
        Me.AddRulesFromRuleSet(ruleSet)
    End Sub


#End Region

#Region "RuleSet Options"


    Private _AcceptNull As Boolean = False
    ''' <summary>
    ''' Accept Null input as Valid
    ''' </summary>
    ''' <value><c>true</c> if accepts null; otherwise, <c>false</c>.</value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property AcceptNull() As Boolean
        Get
            Return _AcceptNull
        End Get
        Set(ByVal value As Boolean)
            _AcceptNull = value
        End Set
    End Property

    Private _AcceptEmpty As Boolean = False
    ''' <summary>
    ''' Accept Empty input as Valid
    ''' </summary>
    ''' <value><c>true</c> if accepts empty string; otherwise, <c>false</c>.</value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property AcceptEmpty() As Boolean
        Get
            Return _AcceptEmpty
        End Get
        Set(ByVal value As Boolean)
            _AcceptEmpty = value
        End Set
    End Property

#End Region


#Region "Rules"


    Private _Rules As List(Of IRule(Of InputType))
    ''' <summary>
    ''' Defined Rules for this Ruleset
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Rules() As List(Of IRule(Of InputType))
        Get
            'Generate Rules
            If _Rules Is Nothing Then
                _Rules = New List(Of IRule(Of InputType))(5)
                ReNewRules()
            End If

            Return _Rules
        End Get
    End Property



    ''' <summary>
    ''' Renew all rules based on configuration.
    ''' </summary>
    ''' <remarks>All extra rules which added to _Rules will be removed.</remarks>
    Private Sub ReNewRules()

        'If this ruleset is not coming from configuration ignore configuration rules
        If Not Me.ElementInformation.IsPresent Then Exit Sub

        'Check & Get all hardcoded rules from 
        For Each RegisteredRule As IRule(Of String) In Me.RegisteredRules
            'If it's not in the config file skip it
            If RegisteredRule Is Nothing OrElse Not CType(RegisteredRule, ConfigurationElement).ElementInformation.IsPresent Then Continue For
            Me._Rules.Add(DirectCast(RegisteredRule, FM.NetBouncer.IRule(Of InputType)))
        Next

        'TODO: FIX: XXX
        ''Get all rules from custom rules
        For Each CustomRule As IRule(Of String) In CustomRuleCollection
            'Load custom rule from DLL and add it, throw an exception if it fails
            Me._Rules.Add(CType(CustomRule, Global.FM.NetBouncer.IRule(Of InputType)))
        Next

    End Sub

    ''' <summary>
    ''' Adds the rules from ruleSet.
    ''' </summary>
    ''' <param name="ruleSet">The ruleSet.</param>
    Public Sub AddRulesFromRuleSet(ByVal ruleSet As RuleSet(Of InputType))
        If ruleSet Is Nothing OrElse ruleSet.Rules.Count = 0 Then Exit Sub
        Me.Rules.AddRange(ruleSet.Rules)
    End Sub


#End Region


#Region "Modifiers"

    Private _Modifiers As New List(Of IModifier(Of InputType))
    ''' <summary>
    ''' Defined Modifiers for this Ruleset
    ''' </summary>
    ''' <value>The modifiers.</value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Modifiers() As List(Of IModifier(Of InputType))
        Get
            Return _Modifiers
        End Get
    End Property



    ''' <summary>
    ''' Modify content as required
    ''' </summary>
    ''' <param name="parameter">The parameter.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Modify(ByVal parameter As InputType) As InputType
        Dim Content As InputType = parameter

        Try

            For Each Modifier As IModifier(Of InputType) In Modifiers
                Content = Modifier.Convert(Content)
            Next

        Catch ex As Exception
            Content = Nothing
            Throw

        End Try

        Return Content
    End Function

#End Region


    ''' <summary>
    ''' Check the parameter against the RuleSet
    ''' </summary>
    ''' <param name="parameter"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function IsParameterValid(ByVal parameter As InputType) As Boolean

        'Check Base if exist
        If Not String.IsNullOrEmpty(Me.BaseRuleSet) Then
            Dim BaseSet As RuleSet(Of String) = NetBouncer.GetInstance.RuleSets(Me.BaseRuleSet)
            If BaseSet Is Nothing Then
                Throw New Exception(Me.BaseRuleSet & " doesn't exist!, If you do not use it remove it.")
                Return False
            End If

            'Check Baseset
            If Not BaseSet.IsParameterValid(Convert.ToString(parameter)) Then Return False
        End If

        'If we accepts nulls and if it's null Return True
        If parameter Is Nothing Then Return Me.AcceptNull

        'Check Empty Strings
        If TypeOf (parameter) Is String Then
            If Convert.ToString(parameter) = String.Empty Then Return Me.AcceptEmpty
        End If


        'Check all rules
        Try
            For Each Rule As IRule(Of InputType) In Me.Rules
                If Not Rule.IsValid(parameter) Then Return False
            Next

        Catch ex As Exception
            Return False

        End Try

        Return True
    End Function





End Class
